-- Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
--
-- this file licensed under the Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
--
-- THIS SOFTWARE IS PROVIDED ON AN \"AS IS\" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
-- PURPOSE.
-- See the Mulan PSL v2 for more details.

local class = require 'mc.class'
local skynet = require 'skynet'
local log = require 'mc.logging'
local utils_core = require 'utils.core'
local base_msg = require 'messages.base'
local base_service = require 'thermal_mgmt.service'

local props = require 'basic_cooling.define.cooling_properties'
local enums = require 'basic_cooling.define.cooling_enums'
local utils = require 'basic_cooling.cooling_utils'
local external_data_keeping = require 'basic_cooling.data_keeping.external_data_keeping'
local air_config = require 'basic_cooling.cooling_config.air_config'
local fans_config = require 'basic_cooling.cooling_config.fans_config'
local pumps_config = require 'basic_cooling.cooling_config.pumps_config'
local requirements = require 'basic_cooling.cooling_requirememts'
local policys = require 'basic_cooling.cooling_policys'
local fans = require 'basic_cooling.cooling_device.cooling_fans'
local cooling_pumps = require 'basic_cooling.cooling_device.cooling_pumps'
local cooling_group = require 'basic_cooling.cooling_device.cooling_group'
local abn_pumps = require "basic_cooling.cooling_abnormal_device.abnormal_pump"
local thermal_subsystem = require 'basic_cooling.cooling_device.thermal_subsystem'

local cooling_objs = class()

local FAN_BOARD_NUM <const> = 1

function cooling_objs:ctor(service, db, bus, persist, register_ipmi_func)
    self.service = service
    self.persist = persist
    self.db = db
    self.bus = bus
    self.cooling_flag = {
        is_base_obj_added = false, -- 调速启动所需要的配置准备完成标志
        cooling_init_service_is_end = false -- 散热初始化服务结束标志位
    }
    self.psr_position = nil
    self.cooling_data = {
        cooling_areas = {},
        abnormal_fans = {},
        policy_area_map = {},
        requirement_area_map = {},
        is_requirement_updated = false
    }
    self.is_fan_obj_check_completed = false
    self.is_pump_obj_check_completed = false
    external_data_keeping.destroy()
    fans.destroy()
    cooling_pumps.destroy()
    cooling_group.destroy()
    requirements.destroy()
    policys.destroy()
    fans_config.destroy()
    pumps_config.destroy()
    abn_pumps.destroy()
    air_config.destroy()
    thermal_subsystem.destroy()
end

function cooling_objs:add_cooling_area_callback(class_name, object, position)
    -- 已缓存一份Area，需要判断优先级
    local old_cooling_area_obj = self.cooling_data.cooling_areas[object.AreaId]
    if old_cooling_area_obj then
        -- 高 -> 低 直接退出不做操作
        if object[props.COOLING_AREA_PRIORITY] < old_cooling_area_obj[props.COOLING_AREA_PRIORITY] then
            return
        end
        -- 低 -> 高 先删除低优先级配置，再新增高优先级配置
        for _, policy_idx in pairs(old_cooling_area_obj.PolicyIdxGroup) do
            self.cooling_data.policy_area_map[policy_idx] = nil
        end
        self.cooling_data.requirement_area_map[old_cooling_area_obj.RequirementIdx] = nil
        self.cooling_data.cooling_areas[old_cooling_area_obj.AreaId] = nil
    end
    self.cooling_data.cooling_areas[object.AreaId] = object
    for _, policy_idx in pairs(object.PolicyIdxGroup) do
        -- 一个Policy唯一对应一个Area
        if self.cooling_data.policy_area_map[policy_idx] then
            log:error('Duplicated policy id: ' .. policy_idx)
            error(base_msg.InternalError())
        end
        self.cooling_data.policy_area_map[policy_idx] = object.AreaId
    end

    if self.cooling_data.requirement_area_map[object.RequirementIdx] then
        log:error('Duplicated requirement id(%u) in area', object.RequirementIdx)
        error(base_msg.InternalError())
    else
        self.cooling_data.requirement_area_map[object.RequirementIdx] = object.AreaId
    end
end

function cooling_objs:add_abnormal_fan_callback(class_name, object, position)
    log:debug('[Cooling] Add object callback, class_name: ' .. class_name .. ', object name: ' .. object.name ..
        ', position: ' .. position)
    local old_abnormal_fan_obj = self.cooling_data.abnormal_fans[object.Id]
    -- 如果已经存了值，取高优先级的对象
    if old_abnormal_fan_obj then
        if object[props.ABNORMAL_FAN_PRIORITY] > old_abnormal_fan_obj[props.ABNORMAL_FAN_PRIORITY] then
            self.cooling_data.abnormal_fans[object.Id] = object
        end
        return
    end
    -- 第一次添加
    self.cooling_data.abnormal_fans[object.Id] = object
end

function cooling_objs:get_add_object_callbacks()
    return {
        ['AbnormalFan'] = {
            ['obj'] = self,['func'] = cooling_objs.add_abnormal_fan_callback
        }
    }
end

function cooling_objs:init()
    self.abn_pumps_instance    = abn_pumps.new()
    self.fans_instance         = fans.new()
    self.extern_data           = external_data_keeping.new(self.bus)
    self.pumps_instance        = cooling_pumps.new()
    self.air_config_instance   = air_config.new()
    self.fans_config_instance  = fans_config.new()
    self.pumps_config_instance = pumps_config.new(self.service)
    self.policys_instance      = policys.new(self.db, self.service, self.persist)
    self.requirements_instance = requirements.new(self.db, self.service, self.cooling_data, self.persist)
    self.cooling_group_instance = cooling_group.new()
    self.thermal_subsystem_instance = thermal_subsystem.new(self.service)

    self.fans_instance.obj_add_complete_signal:on(function()
        self:update_base_obj_add_status()
    end)
    self.pumps_instance.obj_add_complete_signal:on(function()
        self:update_base_obj_add_status()
    end)

    self.thermal_subsystem_instance:set_obj(base_service:CreateThermalSubsystem(1, function (object)
        object.ObjectName = 'ThermalSubsystem_1'
    end))
end

-- 添加对象预处理
function cooling_objs:on_objects_preprocess_cb(object)
    if object.class_name == 'CoolingRequirement' then
        return self.requirements_instance:on_preprocess_cb(object)
    end
    if object.class_name == 'CoolingPolicy' then
        return self.policys_instance:on_preprocess_cb(object)
    end
    return true
end

-- 添加对象回调
function cooling_objs:add_objects_callback(class_name, object, position)
    log:debug('[Cooling] Add object callback, class_name: ' .. class_name .. ', object name: ' .. object.name ..
        ', position: ' .. position)

    if class_name == 'CoolingConfig' then
        self.psr_position = position
        self.fans_config_instance:set_obj(object)
        -- 仅存在风扇的机型才注册该资源树对象
        base_service:CreateCoolingPolicies(1)
    elseif class_name == 'LiquidCoolingConfig' then
        self.psr_position = position
        self.pumps_config_instance:set_obj(object)
    elseif class_name == 'CoolingArea' then
        log:debug('[Cooling] Add CoolingArea Id: ' .. object.AreaId)
        self:add_cooling_area_callback(class_name, object, position)
    elseif class_name == 'CoolingFan' then
        self.fans_instance:obj_add_callback(class_name, object, position)
    elseif class_name == 'CoolingPump' then
        self.pumps_instance:obj_add_callback(class_name, object, position)
    elseif class_name == 'CoolingRequirement' then
        self.requirements_instance:obj_add_callback(class_name, object, position)
    elseif class_name == 'CoolingPolicy' then
        self.policys_instance:obj_add_callback(class_name, object, position)
        if self.cooling_flag.is_base_obj_added then
            self.policys_instance:set_policy_updated_flag(true)
        end
    elseif class_name == 'AbnormalPump' then
        self.abn_pumps_instance:obj_add_callback(class_name, object, position)
    elseif class_name == 'AirCoolingConfig' then
        self.air_config_instance:set_obj(object)
    end

    local add_object_callbacks_t = self:get_add_object_callbacks()
    if add_object_callbacks_t[class_name] then
        local func = add_object_callbacks_t[class_name].func
        local obj = add_object_callbacks_t[class_name].obj

        func(obj, class_name, object, position)
    else
        log:debug('[Cooling] Add object: ' .. class_name .. ', do nothing')
    end
end

function cooling_objs:try_to_create_air_cooling_config(position)
    if self.air_config_instance:is_obj_added() or position ~= self.psr_position or
        self.fans_config_instance:get_medium() ~= enums.cooling_mediums.AIR_COOLING then
        return
    end
    self.air_config_instance.object_from_sr = false
    log:notice("Create air cooling config object.")
    local cooling_config_obj = self.fans_config_instance.obj
    self.air_config_instance:set_cooling_config_obj(cooling_config_obj)
    self.air_config_instance:set_obj(base_service:CreateAirCoolingConfig(1, function (object)
        object.CtrlModePersistType = enums.persist_type.Memory
        object.CtrlMode = cooling_config_obj.CtrlMode
        object.CtrlModeResetPersist = enums.modes.Auto
        object.CtrlModePoweroffPersist = enums.modes.Auto
        object.TimeOut = cooling_config_obj.TimeOut
        object.ManualSpeedPercent = cooling_config_obj.ManualLevel
        object.ManualSpeedPercentResetPersist = 0
        object.ManualSpeedPercentPoweroffPersist = 0
        object.SpeedPercentRange = cooling_config_obj.LevelPercentRange
        object.MinAllowedSpeedPercent = cooling_config_obj.MinAllowedFanSpeedPercent
        object.InitialSpeedPercent = cooling_config_obj.InitLevelInStartup
    end))
end

-- 添加对象完成回调, 每次一个sr文件分发完成都去判断
function cooling_objs:add_objects_complete_callback(position)
    log:debug('[Cooling] Add object complete callback, position: ' .. position)
    self.fans_instance:update_objs_recived_status(self.fans_config_instance:get_fan_board_num(), position)
    self.pumps_instance:update_cooling_objs_recived_status(self.pumps_config_instance:get_max_pump_num(), position)
    log:debug(
        '[Cooling] Current obj numbers: CoolingArea(%u), CoolingFan(%u),  CoolingRequirement(%u), CoolingPolicy(%u)',
        utils.size_of(self.cooling_data.cooling_areas), utils.size_of(self.fans_instance:get_objs()),
        utils.size_of(self.requirements_instance:get_objs()), utils.size_of(self.policys_instance:get_objs()))
    self:try_to_create_air_cooling_config(position)
end

-- 判断风扇散热对象是否分发完成
function cooling_objs:update_fan_obj_add_status()
    log:notice('[Cooling] Check config obj fan config: %s, fan obj add complete: %s',
        self.fans_config_instance:get_obj(), self.fans_instance:is_obj_add_complete())
    -- CoolingConfig 以及所有的 CoolingFan对象都分发完成并且CoolingFan的 FanId 都更新了才开始业务
    if self.fans_config_instance:get_obj() and self.fans_instance:is_obj_add_complete() then
        return true
    end
    return false
end

-- 判断泵散热对象是否分发完成
function cooling_objs:update_pump_obj_add_status()
    log:notice('[Cooling] Check config obj pump config: %s, pump obj add complete: %s',
        self.pumps_config_instance:get_obj(), self.pumps_instance:is_obj_add_complete())
    -- LiquidCoolingConfig 以及所有的 CoolingPump对象都分发完成并且CoolingPump的 Id 都更新了才开始业务
    if self.pumps_config_instance:get_obj() and self.pumps_instance:is_obj_add_complete() then
        return true
    end
    return false
end

function cooling_objs:update_base_obj_add_status()
    -- 风扇+泵
    if self.fans_config_instance:get_obj() and self.pumps_config_instance:get_obj() then
        self.cooling_flag.is_base_obj_added = self:update_fan_obj_add_status() and self:update_pump_obj_add_status()
    -- 风扇散热
    elseif self.fans_config_instance:get_obj() then
        self.cooling_flag.is_base_obj_added = self:update_fan_obj_add_status()
    -- 泵散热
    elseif self.pumps_config_instance:get_obj() then
        self.cooling_flag.is_base_obj_added = self:update_pump_obj_add_status()
    -- 无风扇无泵场景
    else
        self.cooling_flag.is_base_obj_added = false
    end
end

-- 删除对象完成回调
function cooling_objs:delete_objects_complete_callback(position)
    log:notice('[Cooling] Delete object complete callback, position: ' .. position)
end

function cooling_objs:arera_delete_action(object)
    log:notice('[Cooling] Delete CoolingArea Id: ' .. object.AreaId)

    if not self.cooling_data.requirement_area_map[object.RequirementIdx] then
        log:error('There is no requirement id(%u) in area', object.RequirementIdx)
        error(base_msg.InternalError())
    else
        self.cooling_data.requirement_area_map[object.RequirementIdx] = nil
    end

    if not self.cooling_data.cooling_areas[object.AreaId] then
        log:error("[Cooling] Delete CoolingArea but object is nil")
        return
    end
    self.cooling_data.cooling_areas[object.AreaId] = nil
end

function cooling_objs:abnormal_fan_delete_action(object)
    log:notice('[Cooling] Delete AbnormalFan Id: ' .. object.Id)
    if not self.cooling_data.abnormal_fans[object.Id] then
        log:error("[Cooling] Delete AbnormalFan but object is nil")
        return
    end
    self.cooling_data.abnormal_fans[object.Id] = nil
end

-- 删除对象回调
function cooling_objs:del_objects_callback(class_name, object, position)
    log:notice('[Cooling] Delete object callback, class_name: ' .. class_name .. ', object name: ' .. object.name ..
        ', position: ' .. position)

    if class_name == 'CoolingRequirement' then
        self.requirements_instance:obj_delete_callback(class_name, object, position)
    elseif class_name == 'CoolingArea' then
        self:arera_delete_action(object)
    elseif class_name == 'CoolingPolicy' then
        self.policys_instance:obj_delete_callback(class_name, object, position)
    elseif class_name == props.ABNORMAL_FAN then
        self:abnormal_fan_delete_action(object)
    elseif class_name == 'AbnormalPump' then
        self.abn_pumps_instance:obj_delete_callback(class_name, object, position)
    elseif class_name == 'CoolingFan' then
        self.fans_instance:obj_delete_callback(class_name, object, position)
    elseif class_name == 'CoolingPump' then
        self.pumps_instance:obj_delete_callback(class_name, object, position)
    elseif class_name == 'ThermalSubsystem' then
        self.thermal_subsystem_instance:obj_delete_callback(class_name, object, position)
    else
        log:notice('[Cooling] Delete object: ' .. class_name .. ', do nothing')
    end
end

return cooling_objs